
package utils

import (
	"sync"
	"time"
	"errors"
	"fmt"
	"log"
)

const EPOCH_TIMESTAMP uint64 = 550281600000

type IdGenerator interface {
	GetId() (uint64, error)
	GetIds(int) ([]uint64, error)
}

var DefaultIdGenerator IdGenerator = NewSnowFlake(0, 0)

func GetId() (uint64, error){
	return DefaultIdGenerator.GetId()
}

func GetIds(expect int) ([]uint64, error) {
	return DefaultIdGenerator.GetIds(expect)
}

type SnowFlake struct {
	mux          sync.Mutex
	nodeId       uint64
	lastTimeStmp uint64
	sequence     uint64
}

func (s *SnowFlake) GetId() (uint64, error) {
	result, err := s.GetIds(1)
	if err != nil {
		return 0, err
	}
	return result[0], nil
}

func (s *SnowFlake) GetIds(expect int) ([]uint64, error) {
	s.mux.Lock()
	defer s.mux.Unlock()

	if expect == 0 {
		return []uint64{}, nil
	}
	if expect > 4000 {
		return nil, errors.New("param 'expect' must less than 4000!")
	}

	var expectBackup = expect
	var result = make([]uint64, 0, expect)
	for loopCount := 0; loopCount < 2; loopCount++ {
		curr := time.Now()
		currStamp := uint64(curr.UnixNano() / 1e6)
		if currStamp < s.lastTimeStmp {
			msg := fmt.Sprintf("Clock went backwards! %d < %d", currStamp, s.lastTimeStmp)
			log.Println(msg)
			return []uint64{}, errors.New(msg)
		}
		if currStamp > s.lastTimeStmp {
			s.sequence = 0
			s.lastTimeStmp = currStamp
		}

		for ; expect > 0; expect-- {
			s.sequence += 1
			if s.sequence > 4095 {
				break
			}
			id := ((currStamp - EPOCH_TIMESTAMP) << 22) | (s.nodeId << 12) | s.sequence
			result = append(result, id)
		}
	}
	if len(result) != expectBackup {
		log.Println("cant not get enough id!")
		return result, errors.New("")
	}
	return result, nil
}

func NewSnowFlake(dcId int, workId int) *SnowFlake {
	nodeId := ((dcId & 0x3) << 8) | (workId & 0xff)
	snowFlake := &SnowFlake{
		nodeId: uint64(nodeId),
	}
	return snowFlake
}

func ParseId(id uint64) (timeStamp, dcId, workerId, sequence uint64) {
	timeStamp = (id >> 22) + EPOCH_TIMESTAMP
	var nodeId = (id >> 12) & 0x3ff
	dcId = nodeId >> 8
	workerId = nodeId & 0xff
	sequence = id & 0xfff
	return timeStamp, dcId, workerId, sequence
}
